Как уменьшить Seq [Либо [A, B]] до Либо [A, Seq [B]]? - PullRequest
45 голосов
/ 29 августа 2011

Учитывая последовательность либо 100 * * с Left, являющимся сообщением об ошибке.Я хочу получить Either[String,Seq[A]], где я получу Right (который будет Seq[A]), если все элементы последовательности равны Right.Если есть хотя бы одно Left (сообщение об ошибке), я хотел бы получить первое сообщение об ошибке или объединение всех сообщений об ошибках.

Конечно, вы можете опубликовать код скаляза, но ятакже интересует код, не использующий его.

Редактировать

Я изменил заголовок, который первоначально запрашивал Either[Seq[A],Seq[B]], чтобы отразить текст сообщения.

Ответы [ 8 ]

28 голосов
/ 29 августа 2011

Редактировать: я пропустил, что заголовок вашего вопроса спрашивал Either[Seq[A],Seq[B]], но я прочитал «Я хотел бы получить первое сообщение об ошибке или объединение всех сообщений об ошибках», и это даст вам первое:

def sequence[A, B](s: Seq[Either[A, B]]): Either[A, Seq[B]] =
  s.foldRight(Right(Nil): Either[A, List[B]]) {
    (e, acc) => for (xs <- acc.right; x <- e.right) yield x :: xs
  }

scala> sequence(List(Right(1), Right(2), Right(3)))
res2: Either[Nothing,Seq[Int]] = Right(List(1, 2, 3))

scala> sequence(List(Right(1), Left("error"), Right(3)))
res3: Either[java.lang.String,Seq[Int]] = Left(error)

Использование Scalaz:

val xs: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))

scala> xs.sequenceU
res0:  scala.util.Either[String,List[Int]] = Right(List(1, 2, 3))
13 голосов
/ 29 августа 2011

Учитывая начальную последовательность xs, вот мое мнение:

xs collectFirst { case x@Left(_) => x } getOrElse
  Right(xs collect {case Right(x) => x})

Это ответ на основную часть вопроса, получая только первую ошибку как Either[String,Seq[A]].Очевидно, что это неправильный ответ на вопрос в заголовке


Чтобы вернуть все ошибки:

val lefts = xs collect {case Left(x) => x }
def rights = xs collect {case Right(x) => x}
if(lefts.isEmpty) Right(rights) else Left(lefts)

Обратите внимание, что rights определен как метод, поэтомуон будет оцениваться только по требованию, при необходимости

9 голосов
/ 15 сентября 2011

Вот код скаляза:

_.sequence

9 голосов
/ 29 августа 2011

Должно сработать:

def unfoldRes[A](x: Seq[Either[String, A]]) = x partition {_.isLeft} match {
  case (Seq(), r) => Right(r map {_.right.get})
  case (l, _) => Left(l map {_.left.get} mkString "\n")
}

Вы делите свой результат на левый и правый, если левый пустой, строите правое, в противном случае строите левый.

3 голосов
/ 17 января 2019

Начиная с Scala 2.13, большинству коллекций предоставляется метод partitionMap, который разделяет элементы на основе функции, которая отображает элементы либо в Right, либо в Left.

В нашем случае нам даже не нужна функция, которая преобразует наш ввод в Right или Left для определения разбиения, поскольку у нас уже есть Right s и Left s. Таким образом, простое использование identity!

Тогда достаточно просто сопоставить результирующий секционированный набор левых и прав на основе наличия левых:

eithers.partitionMap(identity) match {
  case (Nil, rights)       => Right(rights)
  case (firstLeft :: _, _) => Left(firstLeft)
}

// * val eithers: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))
//         => Either[String,List[Int]] = Right(List(1, 2, 3))
// * val eithers: List[Either[String, Int]] = List(Right(1), Left("error1"), Right(3), Left("error2"))
//         => Either[String,List[Int]] = Left("error1")

Подробности промежуточного шага (partitionMap):

List(Right(1), Left("error1"), Right(3), Left("error2")).partitionMap(identity)
// => (List[String], List[Int]) = (List("error1", "error2"), List(1, 3))
3 голосов
/ 30 августа 2011

Опираясь на решение Кевина и немного украдя у Хаскелла тип Either, вы можете создать метод partitionEithers, например, так:

def partitionEithers[A, B](es: Seq[Either[A, B]]): (Seq[A], Seq[B]) =
  es.foldRight (Seq.empty[A], Seq.empty[B]) { case (e, (as, bs)) =>
    e.fold (a => (a +: as, bs), b => (as, b +: bs))
  }

И использовать это для построения своего решения

def unroll[A, B](es: Seq[Either[A, B]]): Either[Seq[A], Seq[B]] = {
  val (as, bs) = partitionEithers(es)
  if (!as.isEmpty) Left(as) else Right(bs)
}
0 голосов
/ 08 января 2014

Мой ответ похож на @Garrett Rowe: Но он использует foldLeft (см. Также: Почему foldRight и reduRight НЕ являются хвостовой рекурсивностью? ) и добавляет к Seq, а не добавляет к Seq (см .: Почему добавление в список плохо? ).

scala> :paste
// Entering paste mode (ctrl-D to finish)

def partitionEitherSeq[A,B](eitherSeq: Seq[Either[A,B]]): (Seq[A], Seq[B]) =
  eitherSeq.foldLeft(Seq.empty[A], Seq.empty[B]) { (acc, next) =>
  val (lefts, rights) = acc
  next.fold(error => (lefts :+ error, rights), result => (lefts, rights :+ result))
}

// Exiting paste mode, now interpreting.

partitionEitherSeq: [A, B](eitherSeq: Seq[Either[A,B]])(Seq[A], Seq[B])

scala> partitionEitherSeq(Seq(Right("Result1"), Left("Error1"), Right("Result2"), Right("Result3"), Left("Error2")))
res0: (Seq[java.lang.String], Seq[java.lang.String]) = (List(Error1, Error2),List(Result1, Result2, Result3))
0 голосов
/ 29 августа 2011

Я не привык использовать Либо - вот мой подход;может быть, есть более элегантные решения:

def condense [A] (sesa: Seq [Either [String, A]]): Either [String, Seq [A]] = {
  val l = sesa.find (e => e.isLeft)
  if (l == None) Right (sesa.map (e => e.right.get)) 
  else Left (l.get.left.get)
}

condense (List (Right (3), Right (4), Left ("missing"), Right (2)))
// Either[String,Seq[Int]] = Left(missing)
condense (List (Right (3), Right (4), Right (1), Right (2)))
// Either[String,Seq[Int]] = Right(List(3, 4, 1, 2))

Left (l.get.left.get) выглядит немного забавно, но l сам по себе является либо [A, B], а не Either [A, Seq [B]],и нуждается в перепаковке.

...